﻿using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;

namespace Compass.Wasm.Client.Common;

/// <summary>
/// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ObservableRangeCollection<T> : ObservableCollection<T>
{
    /// <summary>
    /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class.
    /// </summary>
    public ObservableRangeCollection()
    {
    }

    /// <summary>
    /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
    /// </summary>
    /// <param name="collection">collection: The collection from which the elements are copied.</param>
    /// <exception cref="T:System.ArgumentNullException">The collection parameter cannot be null.</exception>
    public ObservableRangeCollection(IEnumerable<T> collection)
      : base(collection)
    {
    }

    /// <summary>
    /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
    /// </summary>
    public void AddRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Add)
    {
        if (notificationMode != NotifyCollectionChangedAction.Add && notificationMode != NotifyCollectionChangedAction.Reset)
            throw new ArgumentException("Mode must be either Add or Reset for AddRange.", nameof(notificationMode));
        if (collection == null)
            throw new ArgumentNullException(nameof(collection));
        this.CheckReentrancy();
        int count = this.Count;
        if (!this.AddArrangeCore(collection))
            return;
        if (notificationMode == NotifyCollectionChangedAction.Reset)
            this.RaiseChangeNotificationEvents(NotifyCollectionChangedAction.Reset);
        else
            this.RaiseChangeNotificationEvents(NotifyCollectionChangedAction.Add, collection is List<T> ? (List<T>)collection : new List<T>(collection), count);
    }

    /// <summary>
    /// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). NOTE: with notificationMode = Remove, removed items starting index is not set because items are not guaranteed to be consecutive.
    /// </summary>
    public void RemoveRange(
      IEnumerable<T> collection,
      NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Reset)
    {
        if (notificationMode != NotifyCollectionChangedAction.Remove && notificationMode != NotifyCollectionChangedAction.Reset)
            throw new ArgumentException("Mode must be either Remove or Reset for RemoveRange.", nameof(notificationMode));
        if (collection == null)
            throw new ArgumentNullException(nameof(collection));
        this.CheckReentrancy();
        if (notificationMode == NotifyCollectionChangedAction.Reset)
        {
            bool flag = false;
            foreach (T obj in collection)
            {
                this.Items.Remove(obj);
                flag = true;
            }
            if (!flag)
                return;
            this.RaiseChangeNotificationEvents(NotifyCollectionChangedAction.Reset);
        }
        else
        {
            List<T> changedItems = new List<T>(collection);
            for (int index = 0; index < changedItems.Count; ++index)
            {
                if (!this.Items.Remove(changedItems[index]))
                {
                    changedItems.RemoveAt(index);
                    --index;
                }
            }
            if (changedItems.Count == 0)
                return;
            this.RaiseChangeNotificationEvents(NotifyCollectionChangedAction.Remove, changedItems);
        }
    }

    /// <summary>
    /// Clears the current collection and replaces it with the specified item.
    /// </summary>
    public void Replace(T item) => this.ReplaceRange((IEnumerable<T>)new T[1]
    {
      item
    });

    /// <summary>
    /// Clears the current collection and replaces it with the specified collection.
    /// </summary>
    public void ReplaceRange(IEnumerable<T> collection)
    {
        if (collection == null)
            throw new ArgumentNullException(nameof(collection));
        this.CheckReentrancy();
        int num1 = this.Items.Count == 0 ? 1 : 0;
        this.Items.Clear();
        this.AddArrangeCore(collection);
        int num2 = this.Items.Count == 0 ? 1 : 0;
        if ((num1 & num2) != 0)
            return;
        this.RaiseChangeNotificationEvents(NotifyCollectionChangedAction.Reset);
    }

    private bool AddArrangeCore(IEnumerable<T> collection)
    {
        bool flag = false;
        foreach (T obj in collection)
        {
            this.Items.Add(obj);
            flag = true;
        }
        return flag;
    }

    private void RaiseChangeNotificationEvents(
      NotifyCollectionChangedAction action,
      List<T>? changedItems = null,
      int startingIndex = -1)
    {
        this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
        this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        if (changedItems == null)
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(action));
        else
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, (IList)changedItems, startingIndex));
    }
}